package cn.orangeframe.neon.application.run;

import cn.orangeframe.neon.application.exception.TerminateWorkflowException;
import cn.orangeframe.neon.application.mapper.TaskMapper;
import cn.orangeframe.neon.application.mapper.TaskMapperContext;
import cn.orangeframe.neon.application.util.IDGenerator;
import cn.orangeframe.neon.application.util.ParametersUtils;
import cn.orangeframe.neon.application.util.TaskUtils;
import cn.orangeframe.neon.domain.entity.WorkflowDef;
import cn.orangeframe.neon.domain.entity.WorkflowTask;
import cn.orangeframe.neon.domain.enums.TaskStatus;
import cn.orangeframe.neon.domain.enums.TaskType;
import cn.orangeframe.neon.domain.enums.WorkflowStatus;
import cn.orangeframe.neon.domain.model.BeanConvert;
import cn.orangeframe.neon.domain.model.TaskModel;
import cn.orangeframe.neon.domain.model.WorkflowModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

import static cn.orangeframe.neon.domain.enums.TaskStatus.*;
import static cn.orangeframe.neon.domain.enums.TaskType.TERMINATE;

@Slf4j
@Service
public class DeciderService {

    private final Map<String, TaskMapper> taskMapperMap;
    private final BeanConvert convert;
    private final ParametersUtils parametersUtils;

    public DeciderService(Map<String, TaskMapper> taskMapperMap, BeanConvert convert, ParametersUtils parametersUtils) {
        this.taskMapperMap = taskMapperMap;
        this.convert = convert;
        this.parametersUtils = parametersUtils;
    }

    public DeciderOutcome decide(WorkflowModel workflow) throws TerminateWorkflowException {
        final List<TaskModel> tasks = workflow.getTasks();

        List<TaskModel> tasksToBeScheduled = new LinkedList<>();
        if (tasks.isEmpty()) {
            tasksToBeScheduled = startWorkflow(workflow);
            if (tasksToBeScheduled == null) {
                tasksToBeScheduled = new LinkedList<>();
            }
        }
        return decide(workflow, tasksToBeScheduled);
    }

    public DeciderOutcome decide(final WorkflowModel workflowModel, List<TaskModel> preScheduledTasks) throws TerminateWorkflowException {
        DeciderOutcome outcome = new DeciderOutcome();

        if (workflowModel.getStatus().isTerminal()) {
            log.debug("Workflow {} is already finished. Reason: {}", workflowModel, workflowModel.getReasonForInCompletion());
            outcome.setCompleted(true);
            return outcome;
        }

        Map<String, TaskModel> tasksToBeScheduled = new LinkedHashMap<>();
        boolean hasSuccessfulTerminateTask = false;
        // 新流程传入preScheduleTasks，首次进入将这些任务放入待安排任务列表
        preScheduledTasks.forEach(preScheduledTask -> {
            tasksToBeScheduled.put(preScheduledTask.getTaskReferenceName(), preScheduledTask);
        });

        // 新流程workflow实例的task为空
        List<TaskModel> pendingTasks = new ArrayList<>();
        for (TaskModel task : workflowModel.getTasks()) {
            if (!task.getStatus().equals(SKIPPED) && !task.isExecuted()) {
                pendingTasks.add(task);
            }
            if (TERMINATE.name().equals(task.getTaskType())
                    && task.getStatus().isTerminal()
                    && task.getStatus().isSuccessful()) {
                hasSuccessfulTerminateTask = true;
                outcome.terminateTask = task;
            }
        }
        // 新流程不会走该代码分支
        for (TaskModel pendingTask : pendingTasks) {
            WorkflowTask workflowTask = pendingTask.getWorkflowTask();

            // 对于未执行完且需自动重试的任务，需重复执行
            if (!pendingTask.getStatus().isSuccessful())  {
                Optional<TaskModel> retryTask =
                        retry(workflowTask, pendingTask, workflowModel);
                if (retryTask.isPresent()) {
                    tasksToBeScheduled.put(retryTask.get().getTaskReferenceName(), retryTask.get());
                } else {
                    pendingTask.setStatus(COMPLETED_WITH_ERRORS);
                }
            }

            if (!pendingTask.getStatus().isTerminal()) {
                tasksToBeScheduled.putIfAbsent(pendingTask.getTaskReferenceName(), pendingTask);
            } else if (!pendingTask.isExecuted()) {
                pendingTask.setExecuted(true);
                List<TaskModel> nextTasks = getNextTask(workflowModel, pendingTask);
                if (pendingTask.isLoopOverTask()
                        && !TaskType.DO_WHILE.name().equals(pendingTask.getTaskType())
                        && !nextTasks.isEmpty()) {
                    nextTasks = filterNextLoopOverTasks(nextTasks, pendingTask, workflowModel);
                }
                nextTasks.forEach(nextTask -> tasksToBeScheduled.putIfAbsent(nextTask.getTaskReferenceName(), nextTask));
            }
        }

        // 将所有待安排任务放入返回值
        outcome.taskToSchedule.addAll(tasksToBeScheduled.values());
        if (hasSuccessfulTerminateTask ||
                (outcome.taskToSchedule.isEmpty() && checkForWorkflowCompletion(workflowModel))) {
            log.debug("Marking workflow: {} as complete.", workflowModel.getWorkflowId());
            outcome.completed = true;
        }

        return outcome;
    }

    public List<TaskModel> startWorkflow(final WorkflowModel workflowModel) {
        final WorkflowDef workflowDef = workflowModel.getWorkflowDefinition();

        log.debug("Starting workflow: {}", workflowModel.getWorkflowId());

        List<TaskModel> tasks = workflowModel.getTasks();
        if (tasks.isEmpty()) {
            if (workflowDef.getTasks().isEmpty()) {
                throw new TerminateWorkflowException("No tasks found to be executed", WorkflowStatus.COMPLETED);
            }

            // 启动任务默认使用第一个任务
            WorkflowTask taskToSchedule = workflowDef.getTasks().getFirst();
            while (isTaskSkipped(taskToSchedule, workflowModel)) {
                taskToSchedule = workflowDef.getNextTask(taskToSchedule.getTaskReferenceName());
            }
            return getTasksToBeScheduled(workflowModel, taskToSchedule, 0);
        }

        return Collections.emptyList();
    }

    public List<TaskModel> getNextTask(WorkflowModel workflow, TaskModel task) {
        final WorkflowDef workflowDef = workflow.getWorkflowDefinition();

        if (TaskType.SWITCH.name().equals(task.getTaskType())) {
            // 对于switch任务的nextTask，由switch任务的最后一个任务去决策
            if (task.getInputData().get("hasChildren") != null) {
                return Collections.emptyList();
            }
        }

        String taskReferenceName =
                task.isLoopOverTask()
                        ? TaskUtils.removeIterationFromTaskRefName(task.getTaskReferenceName())
                        : task.getTaskReferenceName();
        WorkflowTask taskToSchedule = workflowDef.getNextTask(taskReferenceName);
        while (isTaskSkipped(taskToSchedule, workflow)) {
            taskToSchedule = workflowDef.getNextTask(taskToSchedule.getTaskReferenceName());
        }
        if (taskToSchedule != null && TaskType.DO_WHILE.name().equals(taskToSchedule.getType())) {
            // check if already has this DO_WHILE task, ignore it if it already exists
            String nextTaskReferenceName = taskToSchedule.getTaskReferenceName();
            if (workflow.getTasks().stream()
                    .anyMatch(runningTask -> runningTask
                            .getTaskReferenceName()
                            .equals(nextTaskReferenceName))) {
                return Collections.emptyList();
            }
        }
        if (taskToSchedule != null) {
            return getTasksToBeScheduled(workflow, taskToSchedule, 0);
        }
        return Collections.emptyList();
    }

    private boolean isTaskSkipped(WorkflowTask taskToSchedule, WorkflowModel workflow) {
        try {
            boolean isTaskSkipped = false;
            if (taskToSchedule != null) {
                TaskModel t = workflow.getTaskByRefName(taskToSchedule.getTaskReferenceName());
                if (t == null) {
                    return false;
                } else if (t.getStatus().equals(SKIPPED)) {
                    isTaskSkipped = true;
                }
            }
            return isTaskSkipped;
        } catch (Exception e) {
            throw new TerminateWorkflowException(e.getMessage());
        }
    }

    public boolean checkForWorkflowCompletion(final WorkflowModel workflow) {
        List<TaskModel> allTasks = workflow.getTasks();
        if (allTasks.isEmpty()) {
            return false;
        }

        Map<String, TaskStatus> taskStatusMap = new HashMap<>();
        workflow.getTasks().forEach(task -> taskStatusMap.put(task.getTaskReferenceName(), task.getStatus()));

        List<WorkflowTask> workflowTasks = workflow.getWorkflowDefinition().getTasks();
        boolean allCompletedSuccessfully = workflowTasks.stream()
                .parallel()
                .allMatch(task -> {
                    TaskStatus status = taskStatusMap.get(task.getTaskReferenceName());
                    return status != null && status.isTerminal();
                });

        boolean noPendingTasks = taskStatusMap.values()
                .stream()
                .allMatch(TaskStatus::isTerminal);

        boolean noPendingSchedule = workflow.getTasks().stream()
                .parallel()
                .noneMatch(wftask -> {
                    String next = getNextTasksToBeScheduled(workflow, wftask);
                    return next != null && !taskStatusMap.containsKey(next);
                });

        return allCompletedSuccessfully && noPendingTasks && noPendingSchedule;
    }

    private String getNextTasksToBeScheduled(WorkflowModel workflow, TaskModel task) {
        final WorkflowDef def = workflow.getWorkflowDefinition();

        String taskReferenceName = task.getTaskReferenceName();
        WorkflowTask taskToSchedule = def.getNextTask(taskReferenceName);
        while (isTaskSkipped(taskToSchedule, workflow)) {
            taskToSchedule = def.getNextTask(taskToSchedule.getTaskReferenceName());
        }
        return taskToSchedule == null ? null : taskToSchedule.getTaskReferenceName();
    }

    public List<TaskModel> getTasksToBeScheduled(
            WorkflowModel workflow, WorkflowTask taskToSchedule, int retryCount) {
        return getTasksToBeScheduled(workflow, taskToSchedule, retryCount, null);
    }

    public List<TaskModel> getTasksToBeScheduled(WorkflowModel workflowModel, WorkflowTask taskToSchedule
            , int retryCount, String retryTaskId) {
        Map<String, Object> input = parametersUtils.getTaskInput(taskToSchedule.getInputParameters(), workflowModel);

        // 当前流程中已存在的任务名
        List<String> tasksInWorkflow = workflowModel.getTasks().stream()
                .map(TaskModel::getTaskReferenceName)
                .toList();
        String taskId = IDGenerator.generate();

        TaskMapperContext context = TaskMapperContext.builder()
                .taskId(taskId)
                .taskToSchedule(taskToSchedule)
                .workflowDefinition(workflowModel.getWorkflowDefinition())
                .workflowInstance(workflowModel)
                .retryCount(retryCount)
                .retryTaskId(retryTaskId)
                .deciderService(this)
                .taskInput(input)
                .build();

        // 获取待调度任务，排除出现过的任务
        return taskMapperMap.get(taskToSchedule.getType())
                .getMappedTasks(context)
                .stream()
                .filter(task -> !tasksInWorkflow.contains(task.getTaskReferenceName()))
                .toList();
    }

    /**
     * 重试任务，判断当前任务是否可重试，如果可以并且没有超过重试次数，则返回新任务
     * @param workflowTask
     * @param task
     * @param workflowModel
     * @return
     */
    public Optional<TaskModel> retry(WorkflowTask workflowTask, TaskModel task, WorkflowModel workflowModel) {
        // 需先根据workflowTask属性判断是否可重试和最大重试次数
        int retryCount = task.getRetryCount();
        final int expectedRetryCount = workflowTask.getRetryCount();
        if (expectedRetryCount <= retryCount) {
            return Optional.empty();
        }

        TaskModel rescheduled = convert.copy(task);
        rescheduled.setRetryCount(task.getRetryCount() + 1);
        rescheduled.setTaskId(IDGenerator.generate());
        rescheduled.setRetriedTaskId(task.getTaskId());
        rescheduled.setStatus(SCHEDULED);
        return Optional.of(rescheduled);
    }

    /**
     * 判断任务是否需要重试
     * @param tasks
     * @param pendingTask
     * @param workflow
     * @return
     */
    List<TaskModel> filterNextLoopOverTasks(
            List<TaskModel> tasks, TaskModel pendingTask, WorkflowModel workflow) {

        // Update the task reference name and iteration
        tasks.forEach(
                nextTask -> {
                    nextTask.setTaskReferenceName(
                            TaskUtils.appendIteration(
                                    nextTask.getTaskReferenceName(), pendingTask.getIteration()));
                    nextTask.setIteration(pendingTask.getIteration());
                });

        List<String> tasksInWorkflow =
                workflow.getTasks().stream()
                        .filter(
                                runningTask ->
                                        runningTask.getStatus().equals(TaskStatus.IN_PROGRESS)
                                                || runningTask.getStatus().isTerminal())
                        .map(TaskModel::getTaskReferenceName)
                        .toList();

        return tasks.stream()
                .filter(
                        runningTask ->
                                !tasksInWorkflow.contains(runningTask.getTaskReferenceName()))
                .collect(Collectors.toList());
    }

    @Data
    public static class DeciderOutcome {
        /**
         * 任务是否完成
         */
        private boolean completed;
        /**
         * 待调度任务
         */
        private List<TaskModel> taskToSchedule = new LinkedList<>();
        /**
         * 中止任务
         */
        private TaskModel terminateTask;

    }
}
